/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spatial_mgr_service_inner.h"

#include "spatial_awareness_common.h"
#include "spatial_awareness_mgr_service.h"

namespace OHOS {
namespace Msdp {
namespace {
const std::string TASK_UNREG_SPATIAL_LOCATION_CALLBACK = "UnRegSpatialLocationpCB";
}

SpatialMgrServiceInner::SpatialMgrServiceInner(const wptr<SpatialaWarenessMgrService>& sams): sams_(sams)
{
}

SpatialMgrServiceInner::~SpatialMgrServiceInner()
{
}

bool SpatialMgrServiceInner::Init()
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    if (spatialLocationCBDeathRecipient_ == nullptr) {
        spatialLocationCBDeathRecipient_ = new SpatialLocationCallbackDeathRecipient();
    }
    return true;
}

void SpatialMgrServiceInner::SubscribeSpatialLocationRelationship(const SpatialLocationMode& mode,
    const SpatialDeviceInfo &info, const sptr<ISpatialLocationCallback>& callback)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);

    std::lock_guard lock(mutex_);
    RETURN_IF(callback == nullptr);
    auto object = callback->AsObject();
    RETURN_IF(object == nullptr);

    DeviceInfo deviceInfo = ConvertDeviceInfo(info);
    std::shared_ptr<CallbackTrigger> callbckTrigger = nullptr;
    switch (mode) {
        case SpatialLocationMode::MODE_DIRECTION:
            callbckTrigger = std::make_shared<DirectionTrigger>(deviceInfo, callback);
            break;
        case SpatialLocationMode::MODE_DISTANCE:
            callbckTrigger = std::make_shared<DistanceTrigger>(deviceInfo, callback);
            break;
        case SpatialLocationMode::MODE_ANGLE:
            callbckTrigger = std::make_shared<AngleTrigger>(deviceInfo, callback);
            break;
        case SpatialLocationMode::MODE_INNER_OUTER:
            callbckTrigger = std::make_shared<InnerOuterTrigger>(deviceInfo, callback);
            break;
        case SpatialLocationMode::MODE_EXIST:
            callbckTrigger = std::make_shared<ExistTrigger>(deviceInfo, callback);
            break;
        default:
            break;
    }

    if (callbckTrigger == nullptr) {
        return;
    }

    spatialLocationCBDeathRecipient_ =
        new SpatialLocationCallbackDeathRecipient(mode, info, callback);
    object->AddDeathRecipient(spatialLocationCBDeathRecipient_);

    auto iter = triggerMap_.find(deviceInfo);
    if (iter == triggerMap_.end()) {
        std::list<std::shared_ptr<CallbackTrigger>> triggers;
        triggers.push_back(callbckTrigger);
        triggerMap_.emplace(deviceInfo, triggers);
    } else {
        iter->second.push_back(callbckTrigger);
    }

    for (auto info : triggerMap_) {
        for (auto triggerIter : info.second) {
            SPATIAL_LOGI(MODULE_SERVICE, "%{public}s trigger callback device res", __func__);
            triggerIter->Trigger(updateCoordinate_);
        }
    }
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s return", __func__);
}

void SpatialMgrServiceInner::UnSubscribeSpatialLocationRelationship(const SpatialLocationMode& mode,
    const SpatialDeviceInfo &info, const sptr<ISpatialLocationCallback>& callback)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::lock_guard lock(mutex_);
    RETURN_IF(callback == nullptr);
    auto object = callback->AsObject();
    RETURN_IF(object == nullptr);

    DeviceInfo deviceInfo = ConvertDeviceInfo(info);
    auto triggers = triggerMap_.find(deviceInfo);
    if (triggers == triggerMap_.end()) {
        return;
    }
    if (triggers->second.size() == 1) {
        if (triggers->second.front()->Match(deviceInfo, mode, callback)) {
            triggerMap_.erase(triggers);
        }
        return;
    }
    for (auto iter = triggers->second.begin(); iter != triggers->second.end(); iter++) {
        if (iter->get()->Match(deviceInfo, mode, callback)) {
            triggers->second.erase(iter);
        }
    }
    object->RemoveDeathRecipient(spatialLocationCBDeathRecipient_);
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s return", __func__);
}

bool SpatialMgrServiceInner::GetMonitoringDevices(std::vector<DeviceInfo>& devices)
{
    if (triggerMap_.empty()) {
        return false;
    }
    for (auto iter = triggerMap_.begin(); iter != triggerMap_.end(); iter++) {
        devices.emplace_back(iter->first);
    }
    return true;
}

void SpatialMgrServiceInner::OnDeviceAdded(const DeviceInfo& device, const DeviceCoordinate& coordinate)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s device=%{public}s", __func__, device.deviceId.c_str());
    updateCoordinate_ = coordinate;
    if (triggerMap_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s triggerMap_ is empty", __func__);
        return;
    }

    auto triggers = triggerMap_.find(device);
    if (triggers != triggerMap_.end()) {
        for (auto iter = triggers->second.begin(); iter != triggers->second.end(); iter++) {
            if (iter->get() != nullptr) {
                SPATIAL_LOGI(MODULE_SERVICE, "%{public}s callback relation", __func__);
                iter->get()->Trigger(coordinate);
            } else {
                continue;
            }
        }
    }
}

void SpatialMgrServiceInner::OnDeviceRemoved(const DeviceInfo& device)
{
    // do nothing
}

void SpatialMgrServiceInner::OnDeviceCoordinateUpdate(const DeviceInfo& device, const DeviceCoordinate& coordinate)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s coordinate.direction=%{public}d", __func__, coordinate.direction);
    updateCoordinate_ = coordinate;
    if (triggerMap_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s triggerMap_ is empty", __func__);
        return;
    }

    auto triggers = triggerMap_.find(device);
    if (triggers != triggerMap_.end()) {
        for (auto iter = triggers->second.begin(); iter != triggers->second.end(); iter++) {
            if (iter->get() != nullptr) {
                SPATIAL_LOGI(MODULE_SERVICE, "%{public}s callback relation", __func__);
                iter->get()->Trigger(coordinate);
            } else {
                continue;
            }
        }
    }
}

void SpatialMgrServiceInner::OnUpdateEnd()
{
    // do nothing
}

void SpatialMgrServiceInner::SpatialLocationCallbackDeathRecipient::OnRemoteDied(const wptr<IRemoteObject>& remote)
{
    if (remote == nullptr || remote.promote() == nullptr) {
        return;
    }
    SPATIAL_LOGI(MODULE_SERVICE,
        "OnRemoteDied::%{public}s remote = %{public}p", __func__, remote.promote().GetRefPtr());
    auto sams = DelayedSpSingleton<SpatialaWarenessMgrService>::GetInstance();
    if (sams == nullptr) {
        return;
    }

    auto handler = sams->GetHandler();
    if (handler == nullptr) {
        return;
    }
    sptr<ISpatialLocationCallback> callback = iface_cast<ISpatialLocationCallback>(remote.promote());
    std::function<void()> unRegFunc = std::bind(&SpatialaWarenessMgrService::UnSubscribeSpatialLocationRelationship,
        sams, mode_, info_, callback_);
    handler->PostTask(unRegFunc, TASK_UNREG_SPATIAL_LOCATION_CALLBACK);
}

DeviceInfo SpatialMgrServiceInner::ConvertDeviceInfo(const SpatialDeviceInfo &info)
{
    DeviceInfo deviceInfo;
    deviceInfo.deviceId = info.GetDeviceId();
    deviceInfo.deviceName = info.GetDeviceName();
    deviceInfo.typeId = static_cast<uint32_t>(info.GetDeviceType());
    return deviceInfo;
}

bool SpatialMgrServiceInner::CallbackTrigger::IsMonitoredDevice(const DeviceInfo &info)
{
    return (device_ == info);
}

bool SpatialMgrServiceInner::CallbackTrigger::Match(const DeviceInfo &info,
    const SpatialLocationMode &mode, const sptr<ISpatialLocationCallback>& callback)
{
    if (device_ == info && mode_ == mode && callback_ == callback) {
        return true;
    }
    return false;
}

bool SpatialMgrServiceInner::DirectionTrigger::Trigger(const DeviceCoordinate& update)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s DirectionTrigger enter", __func__);
    if (coordinate_.direction == update.direction) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s direction not change", __func__);
        return false;
    }
    directionRes_.directionType = DirectionType::TYPE_INVAILD_DIRECTION;
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s DirectionTrigger update.direction=%{public}d", __func__, update.direction);
    switch (update.direction) {
        case SpatialDirection::FRONT:
            directionRes_.directionType = DirectionType::TYPE_FRONT;
            break;
        case SpatialDirection::BACK:
            directionRes_.directionType = DirectionType::TYPE_BACK;
            break;
        case SpatialDirection::LEFT:
            directionRes_.directionType = DirectionType::TYPE_LEFT;
            break;
        case SpatialDirection::RIGHT:
            directionRes_.directionType = DirectionType::TYPE_RIGHT;
            break;
        case SpatialDirection::UP:
            directionRes_.directionType = DirectionType::TYPE_UP;
            break;
        case SpatialDirection::DOWN:
            directionRes_.directionType = DirectionType::TYPE_DOWN;
            break;
        default:
            break;
    }
    if (directionRes_.directionType == DirectionType::TYPE_INVAILD_DIRECTION) {
        return false;
    }
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s DirectionTrigger callback", __func__);
    callback_->OnDirectionChanged(directionRes_);
    coordinate_.direction = update.direction;
    return true;
}

bool SpatialMgrServiceInner::DistanceTrigger::Trigger(const DeviceCoordinate& update)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s DistanceTrigger enter", __func__);
    if (coordinate_.distance == update.distance) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s distance not change", __func__);
        return false;
    }
    distanceRes_.distance = update.distance;
    callback_->OnDistanceChanged(distanceRes_);
    coordinate_.distance = update.distance;
    return true;
}

bool SpatialMgrServiceInner::AngleTrigger::Trigger(const DeviceCoordinate& update)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s AngleTrigger enter", __func__);
    if (coordinate_.angle == update.angle) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s angle not change", __func__);
        return false;
    }
    angleRes_.angle = update.angle;
    callback_->OnAngleChanged(angleRes_);
    coordinate_.angle = update.angle;
    return true;
}

bool SpatialMgrServiceInner::InnerOuterTrigger::Trigger(const DeviceCoordinate& update)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s InnerOuterTrigger enter", __func__);
    if (coordinate_.innerouter == update.innerouter) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s inner and outer not change", __func__);
        return false;
    }

    if ((ioRes_.innerOuterType == InnerOuterType::TYPE_INNER
        && update.innerouter == true) ||
        (ioRes_.innerOuterType == InnerOuterType::TYPE_OUTER
        && update.direction == false)) {
        return false;
    }
    if (update.innerouter == true) {
        ioRes_.innerOuterType = InnerOuterType::TYPE_INNER;
    } else {
        ioRes_.innerOuterType = InnerOuterType::TYPE_OUTER;
    }
    callback_->OnInnerOuterChanged(ioRes_);
    coordinate_.innerouter = update.innerouter;
    return true;
}

bool SpatialMgrServiceInner::ExistTrigger::Trigger(const DeviceCoordinate& update)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s ExistTrigger enter", __func__);
    if (coordinate_.exist == update.exist) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s exist not change", __func__);
        return false;
    }
    if ((existRes_.existType == ExistType::TYPE_EXIST
        && update.exist == true) ||
        (existRes_.existType == ExistType::TYPE_NOT_EXIST
        && update.exist == false)) {
        return false;
    }
    if (update.exist == true) {
        existRes_.existType = ExistType::TYPE_EXIST;
    } else {
        existRes_.existType = ExistType::TYPE_NOT_EXIST;
    }
    callback_->OnExistChanged(existRes_);
    coordinate_.exist = update.exist;
    return true;
}
} // namespace Msdp
} // namespace OHOS